package top.v5it.japi.ddd.core.dao.impl.wrapper;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.func.Func1;
import top.v5it.japi.ddd.core.dao.impl.Condition;
import top.v5it.japi.ddd.core.dao.impl.SqlKeyword;
import top.v5it.japi.ddd.core.dao.impl.Wrapper;
import top.v5it.japi.ddd.core.exception.QueryException;
import top.v5it.japi.ddd.core.util.BeanUtils;

import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Supplier;

/**
 * 查询条件包装
 *
 * @param <T> 实体类型
 * @param <W> 子类类型，用于方法链式调用
 * @date 2023/4/3
 */
public class QueryConditionWrapper<T, W extends QueryConditionWrapper<T, W>> extends OrderWrapper<T, W> {

    protected QueryConditionWrapper(T entity) {
        super(entity);
    }

    /**
     * 添加等于条件
     *
     * @param column 字段名
     * @param value  值
     * @return 当前实例
     */
    public W eq(String column, Object value) {
        return this.appendSegment(column, SqlKeyword.EQ, value);
    }

    /**
     * 添加等于条件
     *
     * @param columnFunc 字段的获取函数
     * @param value      值
     * @return 当前实例
     */
    public W eq(Func1<T, ?> columnFunc, Object value) {
        return this.appendSegment(columnFunc, SqlKeyword.EQ, value);
    }

    /**
     * 添加等于条件
     *
     * @param columnFunc 字段的获取函数
     * @return 当前实例
     */
    public W eq(Func1<T, ?> columnFunc) {
        return this.appendSegment(columnFunc, SqlKeyword.EQ);
    }

    /**
     * 添加不等于条件
     *
     * @param column 字段名
     * @param value  值
     * @return 当前实例
     */
    public W ne(String column, Object value) {
        return this.appendSegment(column, SqlKeyword.NE, value);
    }

    /**
     * 添加不等于条件
     *
     * @param columnFunc 字段的获取函数
     * @param value      值
     * @return 当前实例
     */
    public W ne(Func1<T, ?> columnFunc, Object value) {
        return this.appendSegment(columnFunc, SqlKeyword.NE, value);
    }

    /**
     * 添加不等于条件
     *
     * @param columnFunc 字段的获取函数
     * @return 当前实例
     */
    public W ne(Func1<T, ?> columnFunc) {
        return this.appendSegment(columnFunc, SqlKeyword.NE);
    }

    /**
     * 添加大于条件
     *
     * @param column 字段名
     * @param value  值
     * @return 当前实例
     */
    public W gt(String column, Object value) {
        return this.appendSegment(column, SqlKeyword.GT, value);
    }

    /**
     * 添加大于条件
     *
     * @param columnFunc 字段的获取函数
     * @param value      值
     * @return 当前实例
     */
    public W gt(Func1<T, ?> columnFunc, Object value) {
        return this.appendSegment(columnFunc, SqlKeyword.GT, value);
    }

    /**
     * 添加大于条件
     *
     * @param columnFunc 字段的获取函数
     * @return 当前实例
     */
    public W gt(Func1<T, ?> columnFunc) {
        return this.appendSegment(columnFunc, SqlKeyword.GT);
    }

    /**
     * 添加小于条件
     *
     * @param column 字段名
     * @param value  值
     * @return 当前实例
     */
    public W lt(String column, Object value) {
        return this.appendSegment(column, SqlKeyword.LT, value);
    }

    /**
     * 添加小于条件
     *
     * @param columnFunc 字段的获取函数
     * @param value      值
     * @return 当前实例
     */
    public W lt(Func1<T, ?> columnFunc, Object value) {
        return this.appendSegment(columnFunc, SqlKeyword.LT, value);
    }

    /**
     * 添加小于条件
     *
     * @param columnFunc 字段的获取函数
     * @return 当前实例
     */
    public W lt(Func1<T, ?> columnFunc) {
        return this.appendSegment(columnFunc, SqlKeyword.LT);
    }

    /**
     * 添加大于等于条件
     *
     * @param column 字段名
     * @param value  值
     * @return 当前实例
     */
    public W ge(String column, Object value) {
        return this.appendSegment(column, SqlKeyword.GE, value);
    }

    /**
     * 添加大于等于条件
     *
     * @param columnFunc 字段的获取函数
     * @param value      值
     * @return 当前实例
     */
    public W ge(Func1<T, ?> columnFunc, Object value) {
        return this.appendSegment(columnFunc, SqlKeyword.GE, value);
    }

    /**
     * 添加大于等于条件
     *
     * @param columnFunc 字段的获取函数
     * @return 当前实例
     */
    public W ge(Func1<T, ?> columnFunc) {
        return this.appendSegment(columnFunc, SqlKeyword.GE);
    }

    /**
     * 添加小于等于条件
     *
     * @param column 字段名
     * @param value  值
     * @return 当前实例
     */
    public W le(String column, Object value) {
        return this.appendSegment(column, SqlKeyword.LE, value);
    }

    /**
     * 添加小于等于条件
     *
     * @param columnFunc 字段的获取函数
     * @param value      值
     * @return 当前实例
     */
    public W le(Func1<T, ?> columnFunc, Object value) {
        return this.appendSegment(columnFunc, SqlKeyword.LE, value);
    }

    /**
     * 添加小于等于条件
     *
     * @param columnFunc 字段的获取函数
     * @return 当前实例
     */
    public W le(Func1<T, ?> columnFunc) {
        return this.appendSegment(columnFunc, SqlKeyword.LE);
    }

    /**
     * 添加模糊匹配条件，使用LIKE '%value%'形式
     *
     * @param column 字段名
     * @param value  值
     * @return 当前实例
     */
    public W like(String column, Object value) {
        return this.appendSegment(column, SqlKeyword.LIKE, "%" + value + "%");
    }

    /**
     * 添加模糊匹配条件，使用LIKE '%value%'形式
     *
     * @param columnFunc 字段的获取函数
     * @param value      值
     * @return 当前实例
     */
    public W like(Func1<T, ?> columnFunc, Object value) {
        return this.like(this.mapper(columnFunc), value);
    }

    /**
     * 添加模糊匹配条件，使用LIKE '%value%'形式
     *
     * @param columnFunc 字段的获取函数
     * @return 当前实例
     */
    public W like(Func1<T, ?> columnFunc) {
        final Wrapper.Entry entry = resolve(columnFunc);
        return this.like(entry.getName(), entry.getValue());
    }

    /**
     * 添加模糊匹配条件，使用NOT LIKE '%value%'形式
     *
     * @param column 字段名
     * @param value  值
     * @return 当前实例
     */
    public W notLike(String column, Object value) {
        return this.appendSegment(column, SqlKeyword.NOT_LIKE, "%" + value + "%");
    }

    /**
     * 添加模糊匹配条件，使用NOT LIKE '%value%'形式
     *
     * @param columnFunc 字段的获取函数
     * @param value      值
     * @return 当前实例
     */
    public W notLike(Func1<T, ?> columnFunc, Object value) {
        return this.notLike(this.mapper(columnFunc), value);
    }

    /**
     * 添加模糊匹配条件，使用NOT LIKE '%value%'形式
     *
     * @param columnFunc 字段的获取函数
     * @return 当前实例
     */
    public W notLike(Func1<T, ?> columnFunc) {
        final Wrapper.Entry entry = resolve(columnFunc);
        return this.notLike(entry.getName(), entry.getValue());
    }

    /**
     * 添加模糊匹配条件，使用LIKE 'value%'形式
     *
     * @param column 字段名
     * @param value  值
     * @return 当前实例
     */
    public W likeLeft(String column, Object value) {
        return this.appendSegment(column, SqlKeyword.LIKE, "%" + value);
    }

    /**
     * 添加模糊匹配条件，使用LIKE 'value%'形式
     *
     * @param columnFunc 字段的获取函数
     * @param value      值
     * @return 当前实例
     */
    public W likeLeft(Func1<T, ?> columnFunc, Object value) {
        return this.likeLeft(this.mapper(columnFunc), value);
    }

    /**
     * 添加模糊匹配条件，使用LIKE 'value%'形式
     *
     * @param columnFunc 字段的获取函数
     * @return 当前实例
     */
    public W likeLeft(Func1<T, ?> columnFunc) {
        final Wrapper.Entry entry = resolve(columnFunc);
        return this.likeLeft(entry.getName(), entry.getValue());
    }

    /**
     * 添加模糊匹配条件，使用LIKE '%value'形式
     *
     * @param column 字段名
     * @param value  值
     * @return 当前实例
     */
    public W likeRight(String column, Object value) {
        return this.appendSegment(column, SqlKeyword.LIKE, value + "%");
    }

    /**
     * 添加模糊匹配条件，使用LIKE '%value'形式
     *
     * @param columnFunc 字段的获取函数
     * @param value      值
     * @return 当前实例
     */
    public W likeRight(Func1<T, ?> columnFunc, Object value) {
        return this.likeRight(this.mapper(columnFunc), value);
    }

    /**
     * 添加模糊匹配条件，使用LIKE '%value'形式
     *
     * @param columnFunc 字段的获取函数
     * @return 当前实例
     */
    public W likeRight(Func1<T, ?> columnFunc) {
        final Wrapper.Entry entry = resolve(columnFunc);
        return this.likeRight(entry.getName(), entry.getValue());
    }

    /**
     * 添加IN条件，判断字段值是否在指定集合内
     *
     * @param column 字段名
     * @param value  值集合
     * @return 当前实例
     */
    public W in(String column, Object value) {
        return this.appendSegmentLeft(column, SqlKeyword.IN, value);
    }

    /**
     * 添加IN条件，判断字段值是否在指定集合内
     *
     * @param columnFunc 字段的获取函数
     * @param value      值集合
     * @return 当前实例
     */
    public W in(Func1<T, ?> columnFunc, Object value) {
        return this.in(this.mapper(columnFunc), value);
    }

    /**
     * 添加NOT IN条件，判断字段值是否不在指定集合内
     *
     * @param column 字段名
     * @param value  值集合
     * @return 当前实例
     */
    public W not(String column, Object value) {
        return this.appendSegmentLeft(column, SqlKeyword.NOT_IN, value);
    }

    /**
     * 添加NOT IN条件，判断字段值是否不在指定集合内
     *
     * @param columnFunc 字段的获取函数
     * @param value      值集合
     * @return 当前实例
     */
    public W not(Func1<T, ?> columnFunc, Object value) {
        return this.not(this.mapper(columnFunc), value);
    }

    /**
     * 添加全部等于条件
     *
     * @param param 条件参数的键值对
     * @return 当前实例
     */
    public W allEq(Map<String, Object> param) {
        param.forEach(this::eq);
        return wrapper;
    }

    /**
     * 添加全部等于条件
     *
     * @param param 条件参数的键值对，其中键为字段获取函数，值为字段的值
     * @return 当前实例
     */
    public W allEqFunc(Map<Func1<T, ?>, Object> param) {
        param.forEach(this::eq);
        return wrapper;
    }

    /**
     * 添加全部等于条件
     *
     * @param collection 条件参数集合，其中字段获取函数
     * @return 当前实例
     */
    public W allEqFunc(Collection<Func1<T, ?>> collection) {
        collection.forEach(this::eq);
        return wrapper;
    }

    /**
     * 添加字段为空条件
     *
     * @param column 字段名
     * @return 当前实例
     */
    public W isNull(String column) {
        return this.appendSegment(column, SqlKeyword.IS_NULL);
    }

    /**
     * 添加字段为空条件
     *
     * @param columnFunc 字段的获取函数
     * @return 当前实例
     */
    public W isNull(Func1<T, ?> columnFunc) {
        return this.isNull(this.mapper(columnFunc));
    }

    /**
     * 添加OR条件
     *
     * @param supplier 自行构造OR条件语句的函数
     * @return 当前实例
     */
    public W or(Supplier<String> supplier) {
        return this.appendSegment(SqlKeyword.OR, supplier);
    }

    /**
     * 添加EXISTS条件
     *
     * @param supplier 自行构造EXISTS条件语句的函数
     * @return 当前实例
     */
    public W exists(Supplier<String> supplier) {
        return this.appendSegment(SqlKeyword.EXISTS, supplier);
    }

    /**
     * 添加NOT EXISTS条件
     *
     * @param supplier 自行构造NOT EXISTS条件语句的函数
     * @return 当前实例
     */
    public W notExists(Supplier<String> supplier) {
        return this.appendSegment(SqlKeyword.NOT_EXISTS, supplier);
    }

    /**
     * 添加IN条件，判断字段值是否在指定集合内
     *
     * @param column   字段名
     * @param supplier 自行构造IN条件语句的函数
     * @return 当前实例
     */
    public W in(String column, Supplier<String> supplier) {
        return this.appendSegment(column, SqlKeyword.IN, supplier);
    }

    /**
     * 添加IN条件，判断字段值是否在指定集合内
     *
     * @param columnFunc 字段的获取函数
     * @param supplier   自行构造IN条件语句的函数
     * @return 当前实例
     */
    public W in(Func1<T, ?> columnFunc, Supplier<String> supplier) {
        return this.in(this.mapper(columnFunc), supplier);
    }

    /**
     * 添加NOT IN条件，判断字段值是否不在指定集合内
     *
     * @param column   字段名
     * @param supplier 自行构造NOT IN条件语句的函数
     * @return 当前实例
     */
    public W not(String column, Supplier<String> supplier) {
        return this.appendSegment(column, SqlKeyword.NOT_IN, supplier);
    }

    /**
     * 添加NOT IN条件，判断字段值是否不在指定集合内
     *
     * @param columnFunc 字段的获取函数
     * @param supplier   自行构造NOT IN条件语句的函数
     * @return 当前实例
     */
    public W not(Func1<T, ?> columnFunc, Supplier<String> supplier) {
        return this.not(this.mapper(columnFunc), supplier);
    }

    /**
     * 添加字段不为空条件
     *
     * @param column 字段名
     * @return 当前实例
     */
    public W isNotNull(String column) {
        return this.appendSegment(column, SqlKeyword.IS_NOT_NULL);
    }

    /**
     * 添加字段不为空条件
     *
     * @param columnFunc 字段的获取函数
     * @return 当前实例
     */
    public W isNotNull(Func1<T, ?> columnFunc) {
        return this.isNotNull(this.mapper(columnFunc));
    }

    /**
     * 添加区间条件
     *
     * @param column 字段名
     * @param first  起始值
     * @param next   结束值
     * @return 当前实例
     */
    public W between(String column, Object first, Object next) {
        Assert.notNull(first, () -> new QueryException("[" + SqlKeyword.BETWEEN + "]，参数[first]不能为空"));
        Assert.notNull(next, () -> new QueryException("[" + SqlKeyword.BETWEEN + "]，参数[next]不能为空"));
        return this.appendSegment(column, SqlKeyword.BETWEEN, first, next);
    }

    /**
     * 添加区间条件
     *
     * @param columnFunc 字段的获取函数
     * @param first      起始值
     * @param next       结束值
     * @return 当前实例
     */
    public W between(Func1<T, ?> columnFunc, Object first, Object next) {
        return this.between(this.mapper(columnFunc), first, next);
    }

    /**
     * 添加非区间条件
     *
     * @param column 字段名
     * @param first  起始值
     * @param next   结束值
     * @return 当前实例
     */
    public W notBetween(String column, Object first, Object next) {
        Assert.notNull(first, () -> new QueryException("[" + SqlKeyword.BETWEEN + "]，参数[first]不能为空"));
        Assert.notNull(next, () -> new QueryException("[" + SqlKeyword.BETWEEN + "]，参数[next]不能为空"));
        return this.appendSegment(column, SqlKeyword.NOT_BETWEEN, first, next);
    }

    /**
     * 添加非区间条件
     *
     * @param columnFunc 字段的获取函数
     * @param first      起始值
     * @param next       结束值
     * @return 当前实例
     */
    public W notBetween(Func1<T, ?> columnFunc, Object first, Object next) {
        return this.notBetween(this.mapper(columnFunc), first, next);
    }

    private String segment(String column, SqlKeyword keyword) {
        return String.format("`%s` %s", column, keyword.getKeyword());
    }

    private BiFunction<String, String, String> segment(SqlKeyword keyword) {
        return (column, segment) -> {
            switch (keyword) {
                case OR:
                    return String.format("(%s)", segment);
                case EXISTS:
                case NOT_EXISTS:
                    return String.format("%s (%s)", keyword.getKeyword(), segment);
                case IN:
                case NOT_IN:
                    return String.format("`%s` %s (%s)", column, keyword.getKeyword(), segment);
                case IS_NULL:
                case IS_NOT_NULL:
                case LIKE:
                case NOT_LIKE:
                case BETWEEN:
                case NOT_BETWEEN:
                case EQ:
                case NE:
                case GT:
                case LT:
                case GE:
                case LE:
                    return this.segment(column, keyword);
                default:
                    return "";
            }
        };
    }

    private W appendSegment(SqlKeyword keyword, Supplier<String> supplier) {
        return this.appendSegment(null, keyword, supplier, null);
    }

    private W appendSegment(String column, SqlKeyword keyword) {
        return appendSegment(column, keyword, () -> null);
    }

    private W appendSegment(String column, SqlKeyword keyword, Object... value) {
        Assert.notBlank(column, () -> new QueryException("[" + keyword + "]，参数[column]不能为空"));
        if (Objects.isNull(value)) return wrapper;
        return appendSegment(column, keyword, () -> null, value);
    }

    private W appendSegment(Func1<T, ?> columnFunc, SqlKeyword keyword, Object... value) {
        Assert.notNull(columnFunc, () -> new QueryException("[" + keyword + "]，参数[columnFunc]不能为空"));
        final String column = this.mapper(columnFunc);
        if (Objects.isNull(value)) {
            final Object obj = BeanUtils.getValueByField(getEntity(), column);
            if (Objects.isNull(obj)) return wrapper;
            return appendSegment(column, keyword, () -> null, obj);
        }
        return appendSegment(column, keyword, () -> null, value);
    }

    private W appendSegment(String column, SqlKeyword keyword, Supplier<String> supplier) {
        Assert.notBlank(column, () -> new QueryException("[" + keyword + "]，参数[column]不能为空"));
        return appendSegment(column, keyword, supplier, null);
    }

    private W appendSegment(String column, SqlKeyword keyword, Supplier<String> supplier, Object... value) {
        Assert.notNull(supplier, () -> new QueryException("[" + keyword + "]，参数[supplier]不能为空"));
        final String segment = segment(keyword).apply(column, supplier.get());
        final Condition condition = new Condition().setSegment(segment);
        if (value != null && value.length == 1) condition.setFirst(value[0]);
        else if (value != null && value.length == 2) condition.setFirst(value[0]).setNext(value[1]);
        addCondition(condition);
        return wrapper;
    }

    private W appendSegmentLeft(String column, SqlKeyword keyword, Object first) {
        Assert.notBlank(column, () -> new QueryException("[" + keyword + "]，参数[column]不能为空"));
        final Condition condition = new Condition()
                .setSegment(this.segment(column, keyword)).setKeyword(keyword.getKeyword()).setFirst(first);
        addCondition(condition);
        return wrapper;
    }
}
